#include "Cannon.h"

#include "CannonBall.h"

#include "../../Game.h"
#include "../../PhysicsLogic.h"
#include "../../SurfaceRenderingData.h"
#include "../../Renderer.h"
#include "../../Textures.h"
#include "../../Util/Physx.h"
#include "../../Sound.h"
#include "../../ShapeData.h"

LinkedList<Cannon*> Cannon::destoyed;

const float Cannon::maxFirepower = 50.0f;
const float Cannon::minFirepower = 15.0f;

const float Cannon::modelScale = 1.0f;

NxVec3 Cannon::groundBarsOffset(0, 0.067f * modelScale, 0);
NxVec3 Cannon::middleBarsOffset(0, 0.15f * modelScale, 0);
NxVec3 Cannon::cannonOffset(0, 1.529f * modelScale, 0);

Model Cannon::modelGroundBars;
Model Cannon::modelMiddleBars;
Model Cannon::modelCannon;

NxBodyDesc Cannon::bdGroundBars;
NxBodyDesc Cannon::bdMiddleBars;
NxBodyDesc Cannon::bdCannon;

NxActorDesc Cannon::adGroundBars;
NxActorDesc Cannon::adMiddleBars;
NxActorDesc Cannon::adCannon;

NxSpringDesc Cannon::spingdBars;
NxSpringDesc Cannon::spingdCannon;

NxJointLimitPairDesc Cannon::jlpdBars;
NxJointLimitPairDesc Cannon::jlpdCannon;

const float Cannon::firepowerStep = 1.0f;
const float Cannon::rotationForce = 4.0f;

const float Cannon::cannonDensity = 1.0f;
const float Cannon::barsDensity = 1.0f;

const float Cannon::damping = 0.0f;
const float Cannon::jointDamping = 15.0f;

void Cannon::Load() { //static
	NxPhysicsSDK* physicsSDK = PhysicsLogic::GetPhysicsSDK();
	ModelBoundingVolume::SHAPETYPE modelShapeType = ModelBoundingVolume::CONVEX_SHAPE;

	modelGroundBars.SetLog(&Game::log);
	modelMiddleBars.SetLog(&Game::log);
	modelCannon.SetLog(&Game::log);

	modelGroundBars.Load("res/models/bodenplatte.obj", modelScale, physicsSDK, modelShapeType);
	modelMiddleBars.Load("res/models/dreharm.obj", modelScale, physicsSDK, modelShapeType);
	modelCannon.Load("res/models/cannon2.obj", modelScale, physicsSDK, modelShapeType);

	//body descriptions
	bdGroundBars.angularDamping	= damping;
	bdMiddleBars.angularDamping	= damping;
	bdCannon.angularDamping	= damping;


	//actor descriptions
	Modelpart **modelpartsGroundBars = modelGroundBars.GetModelparts();
	for(int i = 0; i < modelGroundBars.GetNbModelparts(); ++i) {
		ModelBoundingVolume *bv = modelpartsGroundBars[i]->GetBoundingVolume();
		if(NxBoxShapeDesc *shDesc = bv->IsBoxShape()) {
			adGroundBars.shapes.pushBack(shDesc);
		} else if(NxConvexShapeDesc *shDesc = bv->IsConvexShape())	{
			adGroundBars.shapes.pushBack(shDesc);
		} else if(NxTriangleMeshShapeDesc *shDesc = bv->IsTriangleMeshShape()) {
			adGroundBars.shapes.pushBack(shDesc);
		}
	}
	adGroundBars.body = &bdGroundBars;
	adGroundBars.density = barsDensity;

	Modelpart **modelpartsMiddleBars = modelMiddleBars.GetModelparts();
	for(int i = 0; i < modelMiddleBars.GetNbModelparts(); ++i) {
		ModelBoundingVolume *bv = modelpartsMiddleBars[i]->GetBoundingVolume();
		if(NxBoxShapeDesc *shDesc = bv->IsBoxShape()) {
			adMiddleBars.shapes.pushBack(shDesc);
		} else if(NxConvexShapeDesc *shDesc = bv->IsConvexShape()) {
			adMiddleBars.shapes.pushBack(shDesc);
		} else if(NxTriangleMeshShapeDesc *shDesc = bv->IsTriangleMeshShape()) {
			adMiddleBars.shapes.pushBack(shDesc);
		}
	}
	adMiddleBars.body = &bdGroundBars;
	adMiddleBars.density = barsDensity;

	Modelpart **modelpartsCannon = modelCannon.GetModelparts();
	for(int i = 0; i < modelCannon.GetNbModelparts(); ++i) {
		ModelBoundingVolume *bv = modelpartsCannon[i]->GetBoundingVolume();
		if(NxBoxShapeDesc *shDesc = bv->IsBoxShape()) {
			adCannon.shapes.pushBack(shDesc);
		} else if(NxConvexShapeDesc *shDesc = bv->IsConvexShape()) {
			adCannon.shapes.pushBack(shDesc);
		} else if(NxTriangleMeshShapeDesc *shDesc = bv->IsTriangleMeshShape()) {
			adCannon.shapes.pushBack(shDesc);
		}
	}

	adCannon.body = &bdGroundBars;
	adCannon.density = cannonDensity;

	//joint spring descriptions
	spingdBars.damper = jointDamping;
	spingdBars.spring = 0;
	spingdBars.targetValue = 0;

	spingdCannon.damper = jointDamping;
	spingdCannon.spring = 0;
	spingdCannon.targetValue = 0;

	//joint limits
	NxJointLimitDesc jldBarsLow;
	jldBarsLow.value = -NxPiF32*0.303f;

	NxJointLimitDesc jldBarsHigh;
	jldBarsHigh.value = NxPiF32*0.303f;

	jlpdBars.low = jldBarsLow;
	jlpdBars.high = jldBarsHigh;

	NxJointLimitDesc jldCannonLow;
	jldCannonLow.value = -NxPiF32*0.35f;

	NxJointLimitDesc jldCannonHigh;
	jldCannonHigh.value = 0;//-NxPiF32*0.103f;

	jlpdCannon.low = jldCannonLow;
	jlpdCannon.high = jldCannonHigh;
}

Cannon::Cannon(const NxVec3& pos, const NxVec3& orientation) {
	Game::log.LogDebug(StringBuilder() << "Cannon " << this);

	this->hp = 5000.0f;
	this->firepower = 27.0f;

	NxScene* scene = PhysicsLogic::GetScene();
	if(scene == NULL) {
		Game::log.LogError("Creating cannon with scene == NULL");
		aGroundBars = NULL;
		aMiddleBars = NULL;
		aCannon = NULL;

		jBars = NULL;
		jCannon = NULL;
	} else {
		NxMat33 rotMat;
		rotationVectorToMatrix(orientation, &rotMat);

		//actors
		aGroundBars = scene->createActor(adGroundBars);
		aGroundBars->setGlobalOrientation(rotMat);
		aGroundBars->setGlobalPosition(pos + rotMat*groundBarsOffset);
		aGroundBars->userData = this;
		aGroundBars->setGroup(1);
		aGroundBars->setContactReportFlags(NX_NOTIFY_FORCES);        

		aMiddleBars = scene->createActor(adMiddleBars);
		aMiddleBars->setGlobalOrientation(rotMat);
		aMiddleBars->setGlobalPosition(pos + rotMat*middleBarsOffset);
		aMiddleBars->setCMassOffsetLocalPosition(zeroVec3);
		aMiddleBars->userData = this;
		aMiddleBars->setGroup(1);
		aMiddleBars->setContactReportFlags(NX_NOTIFY_FORCES);

		aCannon = scene->createActor(adCannon);
		aCannon->setGlobalOrientation(rotMat);
		aCannon->setGlobalPosition(pos + rotMat*cannonOffset);
		aCannon->setCMassOffsetLocalPosition(zeroVec3);
		aCannon->userData = this;
		aCannon->setGroup(1);
		aCannon->setContactReportFlags(NX_NOTIFY_FORCES);

        //adding userdata to shape
        NxActor* actor[3];
        actor[0] = aGroundBars;
        actor[1] = aMiddleBars;
        actor[2] = aCannon;
        for(int i = 0; i < 3; ++i) {
            NxShape* const* shapes = actor[i]->getShapes();
            NxU32 shapesCount = actor[i]->getNbShapes();
            for(NxU32 i=0; i<shapesCount; ++i) {
                NxShape* shape = shapes[i];
                ShapeData *sd = new ShapeData();
                shape->userData = sd;
            }
        }

		//joint descriptions
		NxRevoluteJointDesc jdBars;
		jdBars.jointFlags |= NX_JF_VISUALIZATION;
		jdBars.actor[0] = aGroundBars;
		jdBars.actor[1] = aMiddleBars;
		jdBars.setGlobalAxis(rotMat*NxVec3(0,1,0));
		jdBars.setGlobalAnchor(pos + rotMat*middleBarsOffset);

		NxRevoluteJointDesc jdCannon;
		jdCannon.jointFlags |= NX_JF_VISUALIZATION;
		jdCannon.actor[0] = aMiddleBars;
		jdCannon.actor[1] = aCannon;
		jdCannon.setGlobalAxis(rotMat*NxVec3(1,0,0));
		jdCannon.setGlobalAnchor(pos + rotMat*cannonOffset);

		//joints
		jBars = (NxRevoluteJoint*)scene->createJoint(jdBars);
		jBars->setSpring(spingdBars);
		jBars->setLimits(jlpdBars);

		jCannon = (NxRevoluteJoint*)scene->createJoint(jdCannon);
		jCannon->setSpring(spingdCannon);
		jCannon->setLimits(jlpdCannon);
	}
}

Cannon::~Cannon() {
	NxScene* scene = PhysicsLogic::GetScene();

	NxActor* actor[3];
	actor[0] = aGroundBars;
	actor[1] = aMiddleBars;
	actor[2] = aCannon;

	for(int a=0; a<=2; ++a) {
		NxShape* const* shapes = actor[a]->getShapes();
		NxU32 shapesCount = actor[a]->getNbShapes();
		for(NxU32 i=0; i<shapesCount; ++i) {
			NxShape* shape = shapes[i];
			if(shape->userData) {
				delete shape->userData;
			}
		}
		scene->releaseActor(*actor[a]);
	}

	scene->releaseJoint(*jBars);
	scene->releaseJoint(*jCannon);

	Game::log.LogDebug(StringBuilder() << "~Cannon " << this);
}

NxVec3 Cannon::GetPosition() {
	return aCannon->getGlobalPosition();
}

NxVec3 Cannon::GetGroundPosition() {
	return aGroundBars->getGlobalPosition();
}

NxVec3 Cannon::GetDirection() {
	const NxVec3 v(0,0,-1);
	return aCannon->getGlobalOrientation() * v;
}

void Cannon::Rotate(DIRECTION_2D direction) {
	if(direction==D2_UP) {
		aCannon->addLocalTorque(NxVec3(rotationForce,0,0));
	} else if(direction==D2_DOWN) {
		aCannon->addLocalTorque(NxVec3(-rotationForce,0,0));
	} else if(direction==D2_RIGHT) {
		aCannon->addLocalTorque(NxVec3(0,-rotationForce,0));
	} else if(direction==D2_LEFT) {
		aCannon->addLocalTorque(NxVec3(0,rotationForce,0));
	}
}

void Cannon::Rotate(DIRECTION_2D direction, float force) {
	const float f = force * 1.0f;
	if(direction==D2_UP) {
		aCannon->addLocalTorque(NxVec3(f,0,0));
	} else if(direction==D2_DOWN) {
		aCannon->addLocalTorque(NxVec3(-f,0,0));
	} else if(direction==D2_RIGHT) {
		aCannon->addLocalTorque(NxVec3(0,-f,0));
	} else if(direction==D2_LEFT) {
		aCannon->addLocalTorque(NxVec3(0,f,0));
	}
}

void Cannon::TakeDamage(float force) {
	hp -= force;

	if(hp <= 0) {
		if(jCannon->getState() != NX_JS_BROKEN) {
			jCannon->setBreakable(0,0);
		}
		if(jBars->getState() != NX_JS_BROKEN) {
			jBars->setBreakable(0,0);
		}
		aCannon->setAngularDamping(0);
		aMiddleBars->setAngularDamping(0);
		aGroundBars->setAngularDamping(0);

		if(Game::GetCurrentPlayer()->GetSelectedCannon() == this && PhysicsLogic::IsCameraLocked()) {
			PhysicsLogic::SetCameraLocked(false);
		}
		for(int i=0; i<2; ++i) {
			Player* pickedPlayer = Game::GetPlayer(i);
			if(pickedPlayer->GetSelectedCannon() == this) {
				pickedPlayer->SetSelectedCannonIndex(-1);
			}
			if(pickedPlayer->cannons.Contains(this)) {
				pickedPlayer->cannons.Remove(this);
				Cannon::destoyed.Add(this);
			}
		}
	}
}

void Cannon::Fire() {
	NxShape* shape = aCannon->getShapes()[0];
	NxVec3 pos = shape->getGlobalPosition();
	NxVec3 dir = shape->getGlobalOrientation() * NxVec3(0,0,-1);
	dir.normalize();
	new CannonBall(Game::GetCurrentPlayer(), pos + dir * 2, dir * firepower, PhysicsLogic::GetWind(), Game::GetCurrentPlayer()->GetExplosiveCannonBallsSelected());
	Sound::Play(Sound::SAMPLE_SHOOT, 0.4f, (pos-PhysicsLogic::GetCamera().GetPosition())*0.15f, PhysicsLogic::GetCameraForward());
}

void Cannon::Draw() {
    bool cannonIsVisible = false;
    NxActor* actor[3];
    actor[0] = aGroundBars;
    actor[1] = aMiddleBars;
    actor[2] = aCannon;
    for(int i = 0; i < 3; ++i) {
        NxShape* const* shapes = actor[i]->getShapes();
        NxU32 shapesCount = actor[i]->getNbShapes();
        for(NxU32 i=0; i<shapesCount; ++i) {
            NxShape* shape = shapes[i];
            if(shape->userData) {
                ShapeData *sd = (ShapeData*)shape->userData;
                cannonIsVisible |= sd->isVisible;
                sd->isVisible = false;
            }
        }
    }

    if(cannonIsVisible) {
        Renderer::ResetMaterial();

        float glMat[16];

        glPushMatrix();
        aGroundBars->getGlobalPose().getColumnMajor44(glMat);
        glMultMatrixf(glMat);
        modelGroundBars.Draw();
        glPopMatrix();

        glPushMatrix();
        aMiddleBars->getGlobalPose().getColumnMajor44(glMat);
        glMultMatrixf(glMat);
        modelMiddleBars.Draw();
        glPopMatrix();

        glPushMatrix();
        aCannon->getGlobalPose().getColumnMajor44(glMat);
        glMultMatrixf(glMat);
        modelCannon.Draw();
        glPopMatrix();
    }	
}

void Cannon::IncreaseFirepower() {
	firepower += firepowerStep;
	if(firepower > maxFirepower) {
		firepower = maxFirepower;
	}
}

void Cannon::DecreaseFirepower() {
	firepower -= firepowerStep;
	if(firepower < minFirepower) {
		firepower = minFirepower;
	}
}

void Cannon::ChangeFirepowerBy(float v) {
	firepower += v;
	if(firepower > maxFirepower) {
		firepower = maxFirepower;
	} else if(firepower < minFirepower) {
		firepower = minFirepower;
	}
}
